// Copyright 2025 Supabase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package asthelpergen

import (
	"go/types"
	"log"
	"slices"
	"strings"

	"github.com/dave/jennifer/jen"
)

const cloneName = "Clone"

// cloneGen creates the deep clone methods for the AST. It works by discovering the types
// that it needs to support, starting from a root interface type. While creating the clone
// method for this root interface, more types that need to be cloned are discovered.
// This continues type by type until all necessary types have been traversed.
type cloneGen struct {
	exclude []string
	file    *jen.File
}

var _ generator = (*cloneGen)(nil)

func newCloneGen(pkgname string, options *CloneOptions) *cloneGen {
	file := jen.NewFile(pkgname)
	addLicenseHeader(file)
	file.Comment("Code generated by asthelpergen. DO NOT EDIT.")

	return &cloneGen{
		exclude: options.Exclude,
		file:    file,
	}
}

func (c *cloneGen) addFunc(name string, code *jen.Statement) {
	c.file.Add(jen.Commentf("%s creates a deep clone of the input.", name))
	c.file.Add(code)
}

func (c *cloneGen) genFile(_ generatorSPI) (string, *jen.File) {
	return "ast_clone.go", c.file
}

// readValueOfType produces code to read the expression of type `t`, and adds the type to the todo-list
func (c *cloneGen) readValueOfType(t types.Type, expr jen.Code, spi generatorSPI) jen.Code {
	switch t.Underlying().(type) {
	case *types.Basic:
		return expr
	case *types.Interface:
		// any/interface{} fields have to be handled manually
		if types.TypeString(t, noQualifier) == "any" || types.TypeString(t, noQualifier) == "interface{}" {
			return expr
		}
	}
	spi.addType(t)
	return jen.Id(cloneName + printableTypeName(t)).Call(expr)
}

// structMethod handles cloning for struct types (value, not pointer)
func (c *cloneGen) structMethod(t types.Type, _ *types.Struct, spi generatorSPI) error {
	typeString := types.TypeString(t, noQualifier)
	funcName := cloneName + printableTypeName(t)
	c.addFunc(funcName,
		jen.Func().Id(funcName).Call(jen.Id("n").Id(typeString)).Id(typeString).Block(
			jen.Return(jen.Op("*").Add(c.readValueOfType(types.NewPointer(t), jen.Op("&").Id("n"), spi))),
		))
	return nil
}

// sliceMethod handles cloning for slice types
func (c *cloneGen) sliceMethod(t types.Type, slice *types.Slice, spi generatorSPI) error {
	typeString := types.TypeString(t, noQualifier)
	funcName := cloneName + printableTypeName(t)

	c.addFunc(funcName,
		jen.Func().Id(funcName).Call(jen.Id("n").Id(typeString)).Id(typeString).Block(
			ifNilReturnNil("n"),
			jen.Id("res").Op(":=").Id("make").Call(jen.Id(typeString), jen.Len(jen.Id("n"))),
			c.copySliceElement(t, slice.Elem(), spi),
			jen.Return(jen.Id("res")),
		))
	return nil
}

// copySliceElement generates code to copy slice elements
func (c *cloneGen) copySliceElement(t types.Type, elType types.Type, spi generatorSPI) jen.Code {
	// For unnamed basic slices (like []byte), use built-in copy
	if !isNamed(t) && isBasic(elType) {
		return jen.Copy(jen.Id("res"), jen.Id("n"))
	}

	// For other types, iterate and clone each element
	spi.addType(elType)
	return jen.For(jen.List(jen.Id("i"), jen.Id("x")).Op(":=").Range().Id("n")).Block(
		jen.Id("res").Index(jen.Id("i")).Op("=").Add(c.readValueOfType(elType, jen.Id("x"), spi)),
	)
}

// basicMethod handles cloning for basic types (int, string, etc.)
// Basic types don't need cloning - they're copied by value
func (c *cloneGen) basicMethod(t types.Type, _ *types.Basic, spi generatorSPI) error {
	return nil
}

// interfaceMethod handles cloning for interface types
func (c *cloneGen) interfaceMethod(t types.Type, iface *types.Interface, spi generatorSPI) error {
	typeString := types.TypeString(t, noQualifier)
	typeName := printableTypeName(t)

	stmts := []jen.Code{ifNilReturnNil("in")}

	var cases []jen.Code
	_ = findImplementations(spi.scope(), iface, func(t types.Type) error {
		typeString := types.TypeString(t, noQualifier)

		// case Type: return CloneType(in)
		block := jen.Case(jen.Id(typeString)).Block(jen.Return(c.readValueOfType(t, jen.Id("in"), spi)))
		switch t := t.(type) {
		case *types.Pointer:
			_, isIface := t.Elem().(*types.Interface)
			if !isIface {
				cases = append(cases, block)
			}

		case *types.Named:
			_, isIface := t.Underlying().(*types.Interface)
			if !isIface {
				cases = append(cases, block)
			}

		default:
			log.Fatalf("unexpected type encountered: %s", typeString)
		}

		return nil
	})

	cases = append(cases,
		jen.Default().Block(
			jen.Comment("this should never happen"),
			jen.Return(jen.Nil()),
		))

	stmts = append(stmts, jen.Switch(jen.Id("in").Op(":=").Id("in").Assert(jen.Id("type")).Block(
		cases...,
	)))

	funcName := cloneName + typeName
	funcDecl := jen.Func().Id(funcName).Call(jen.Id("in").Id(typeString)).Id(typeString).Block(stmts...)
	c.addFunc(funcName, funcDecl)
	return nil
}

// ptrToBasicMethod handles cloning for pointer-to-basic types
func (c *cloneGen) ptrToBasicMethod(t types.Type, _ *types.Basic, spi generatorSPI) error {
	ptr := t.Underlying().(*types.Pointer)
	return c.ptrToOtherMethod(t, ptr, spi)
}

// ptrToOtherMethod handles cloning for pointer types
func (c *cloneGen) ptrToOtherMethod(t types.Type, ptr *types.Pointer, spi generatorSPI) error {
	receiveType := types.TypeString(t, noQualifier)

	funcName := cloneName + printableTypeName(t)
	c.addFunc(funcName,
		jen.Func().Id(funcName).Call(jen.Id("n").Id(receiveType)).Id(receiveType).Block(
			ifNilReturnNil("n"),
			jen.Id("out").Op(":=").Add(c.readValueOfType(ptr.Elem(), jen.Op("*").Id("n"), spi)),
			jen.Return(jen.Op("&").Id("out")),
		))
	return nil
}

// ptrToStructMethod handles cloning for pointer-to-struct types
func (c *cloneGen) ptrToStructMethod(t types.Type, strct *types.Struct, spi generatorSPI) error {
	receiveType := types.TypeString(t, noQualifier)
	funcName := cloneName + printableTypeName(t)

	funcDeclaration := jen.Func().Id(funcName).Call(jen.Id("n").Id(receiveType)).Id(receiveType)

	// If this type is in the exclude list, just return the original
	if slices.Contains(c.exclude, receiveType) {
		c.addFunc(funcName, funcDeclaration.Block(
			jen.Return(jen.Id("n")),
		))
		return nil
	}

	var fields []jen.Code
	for i := 0; i < strct.NumFields(); i++ {
		field := strct.Field(i)
		// Skip basic types (copied in shallow copy) and private fields
		if isBasic(field.Type()) || strings.HasPrefix(field.Name(), "_") {
			continue
		}
		// out.Field = CloneType(n.Field)
		fields = append(fields,
			jen.Id("out").Dot(field.Name()).Op("=").Add(c.readValueOfType(field.Type(), jen.Id("n").Dot(field.Name()), spi)))
	}

	stmts := []jen.Code{
		ifNilReturnNil("n"),
		// Shallow copy: out := *n
		jen.Id("out").Op(":=").Op("*").Id("n"),
	}

	// Deep clone all non-basic fields
	stmts = append(stmts, fields...)

	stmts = append(stmts,
		jen.Return(jen.Op("&").Id("out")),
	)

	c.addFunc(funcName, funcDeclaration.Block(stmts...))
	return nil
}

// Helper functions

func ifNilReturnNil(id string) *jen.Statement {
	return jen.If(jen.Id(id).Op("==").Nil()).Block(jen.Return(jen.Nil()))
}

func isNamed(t types.Type) bool {
	_, x := t.(*types.Named)
	return x
}

func isBasic(t types.Type) bool {
	_, x := t.Underlying().(*types.Basic)
	return x
}
